package com.zoe.onelink.auth.interceptor;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.zoe.onelink.auth.property.OnelinkAuthProperties;
import com.zoe.onelink.auth.token.authenticate.OnelinkTokenAuthenticate;
import com.zoe.onelink.common.constant.OnelinkConstant;
import com.zoe.onelink.common.constant.TraceSpanConstant;
import com.zoe.onelink.common.entity.ResultVO;
import com.zoe.onelink.common.enumeration.ExceptionCode;
import com.zoe.onelink.common.enumeration.ExceptionCodeEnum;
import com.zoe.onelink.common.exception.ApiException;
import com.zoe.onelink.util.OnelinkContextHolder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.MessageSource;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.Locale;
import java.util.Optional;

/**
 * <p>标题: </p>
 * <p>描述: </p>
 * <p>版权: Copyright (c) 2022</p>
 * <p>公司: 智业软件股份有限公司</p>
 *
 * @version: 1.0
 * @author: ruansheng
 * @date: 2022-03-16
 */
@RequiredArgsConstructor
@Slf4j
public class OnelinkTokenInterceptor implements HandlerInterceptor {

    private static final int CALL_TIME_LENGTH = 13;
    private static final String CHARACTER_ENCODING = "UTF-8";
    private final OnelinkAuthProperties authProp;
    private final OnelinkTokenAuthenticate onelinkTokenAuthenticate;
    private final MessageSource messageSource;


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 是否开启权限验证(可结合配置中心动态开关)
        if (!this.authProp.isEnabled()) {
            return true;
        }
        String callTime = request.getHeader(OnelinkConstant.CALL_TIME);
        // 验证callTime有效性
        this.validateCallTime(callTime);
        // 获取用户Token
        String onelinkToken = Optional.ofNullable(request.getHeader(OnelinkConstant.AUTH_TOKEN_HEADER))
                // 暂时兼容一下下划线方式
                .orElse(request.getHeader("onelink_token"));
        // 验证token
        ExceptionCode authenticateResult = this.onelinkTokenAuthenticate.authenticate(onelinkToken);
        if (authenticateResult == null) {
            return true;
        }

        // 构建异常返回结果
        Object errorAttr = request.getAttribute(OnelinkConstant.ATTR_TOKEN_AUTHENTICATE_FAILURE);
        String message = errorAttr == null
                ? this.messageSource.getMessage(authenticateResult.getMessageKey(), null, authenticateResult.getMessageKey(), Locale.getDefault())
                : errorAttr.toString();
        String errorStr = "Path: " + request.getRequestURI();
        String traceId = StrUtil.blankToDefault(OnelinkContextHolder.getString(OnelinkConstant.TRACE_ID), request.getHeader(TraceSpanConstant.B3Propagation.TRACE_ID));
        ResultVO<String> resultVO = ResultVO.<String>failure(authenticateResult, message, errorStr)
                .setData(onelinkToken)
                .setTraceId(traceId);
        log.error("{} {}", message, errorStr);
        // 写入异常返回结果
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding(CHARACTER_ENCODING);
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        PrintWriter writer = response.getWriter();
        writer.write(JSON.toJSONString(resultVO));
        writer.flush();
        writer.close();
        return false;
    }

    private void validateCallTime(String callTime) {
        // 是否验证callTime有效性
        if (!this.authProp.isValidateCallTime()) {
            return;
        }
        if (StrUtil.isEmpty(callTime)) {
            throw ApiException.createEx(ExceptionCodeEnum.CALL_TIME_EXPIRED);
        }
        // 检查callTime长度并验证有效性,两个小时后的请求视为失效
        if (callTime.length() < CALL_TIME_LENGTH
                || (System.currentTimeMillis() - Long.parseLong(callTime.substring(0, 13))) > this.authProp.getCallTimeExpiration().toMillis()) {
            throw ApiException.createEx(ExceptionCodeEnum.CALL_TIME_EXPIRED);
        }
    }

}
